/*
 * ProxyServer.cpp
 *
 *  Created on: Apr 22, 2012
 *      Author: shaohong
 */

#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <strings.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <exception>

#define LISTENQ 5

#include "ProxyServer.h"
#include "HttpRequest.h"
#include "HttpHelper.h"
#include "Utils.h"
#include "AppLogger.h"
#include "ProxySession.h"

using namespace std;

static AppLogger * logger = AppLogger::getInstance("ProxyServer");



struct ProxyServerThreadParams {
	ProxyServer * proxyServer;
	int connfd;
};

static void * serveRequest(void *arg) {
	ProxyServerThreadParams * serverThreadParams =
			static_cast<ProxyServerThreadParams*>(arg);

	ProxyServer * server = serverThreadParams->proxyServer;

	server->increamentThreadNumber();

	// serve this request
	try {
		server->service(serverThreadParams->connfd);

	}catch (...){
		logger->error("met exception ");
		close(serverThreadParams->connfd);
	}

	close(serverThreadParams->connfd);

	server->decrementThreadNumber();

	return (void*)(0);
}

ProxyServer::~ProxyServer() {
	// TODO Auto-generated destructor stub
}

ProxyServer::ProxyServer(struct ProxServerConfig serverConfig) {
	this->serverConfig = serverConfig;
	serverShutingDown = false;

	this->threadNumber = 0;	// the number of running thread
	this->threadNumberMutex = PTHREAD_MUTEX_INITIALIZER;// the mutex to access running thread number
}

// run the proxy server
void ProxyServer::run() {
	logger->info("starting ProxyServer with listening port: %ld, cacheDir=%s",
			serverConfig.proxyPort, serverConfig.cacheDir.c_str());

	int listenfd;
	socklen_t len;
	struct sockaddr_in6 servaddr;

	// create socket for listening
	if ((listenfd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) {
		perror("socket");
		logger->error("failed to create socket!");
		exit(EXIT_FAILURE);
	}

	// bind to the port and all interface addresses
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin6_family = AF_INET6;
	servaddr.sin6_addr = in6addr_any;
	servaddr.sin6_port = htons(serverConfig.proxyPort);

	if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
		perror("bind");
		logger->error("failed to bind to port %ld!", serverConfig.proxyPort);
		exit(EXIT_FAILURE);
	}

	// Set the socket to passive mode, with specified listen queue size
	if (listen(listenfd, LISTENQ) < 0) {
		perror("listen");
		logger->error("failed to listen on port %ld!", serverConfig.proxyPort);
		exit(EXIT_FAILURE);
	}

	// Start waiting for incoming connections, until user interrupts
	for (;;) {

		// wait for incoming connection
		struct sockaddr_in6 cliaddr;
		len = sizeof(cliaddr);

		int connfd = accept(listenfd, (struct sockaddr *) (&cliaddr), &len);

		if (serverShutingDown) {
			close(connfd);
			break;
		}

		if (connfd < 0) {
			perror("accept");
			exit(EXIT_FAILURE);
		}

		char buff[80];
		logger->debug("connection from %s, port %d\n",
				inet_ntop(AF_INET6, &cliaddr.sin6_addr, buff, sizeof(buff)),
				ntohs(cliaddr.sin6_port));

		// fire off the new thread and serve the request
		ProxyServerThreadParams * proxyServerThreadParams =
				new ProxyServerThreadParams;
		proxyServerThreadParams->proxyServer = this;
		proxyServerThreadParams->connfd = connfd;
		pthread_t serviceThread;
		pthread_create(&serviceThread, NULL, serveRequest,
				proxyServerThreadParams);
	}
}

uint32_t ProxyServer::getServerPort() const {

	return this->serverConfig.proxyPort;
}

string ProxyServer::getCacheDir() const {
	return serverConfig.cacheDir;
}


void ProxyServer::service(int connfd) {

	// Read the (initial part of) HTTP Request and parse the headers
	char buffer[1500];
	memset(buffer, 0, sizeof(buffer));

	int headerBytes = HttpHelper::readHttpHeaders(connfd, buffer, sizeof(buffer));
	if (headerBytes == -1){
		return;
	}

	if (!HttpHelper::endsWithSeparator(buffer)) {
		logger->error("HTTP request doesn't contain CRLFCRLF separator!");
		// send error response back to client
		HttpResponse * response = new HttpResponse();

		// we should return 400 Bad Request here
		response->setStatus(400, "Bad Request");
		sendResponse(response, connfd);
	}

	logger->debug("received request:\n%s", buffer);

	HttpRequest * request = NULL;
	request = HttpHelper::parseHttpRequest(string(buffer));

	// create a proxySession and ask it to do the proxying job
	ProxySession * proxySession = new ProxySession(this, connfd, request);

	proxySession->doProxy();

}

// gracefully shutdown the server;
void ProxyServer::gracefulShutDown() {
	logger->info("gracefulShutDown");
	this->serverShutingDown = true;

	while (getThreadNumber() > 0) {
		// wait for running thread to exit;
		sleep(2);
	}

	logger->info("All child threads exited. the server is shutdown now!");

}

void ProxyServer::sendResponse(HttpResponse * response, int connfd) {
	string responseStr = response->generateHeadersStrings();
	Utils::flushOutputBuffer(connfd,
			(char*) (responseStr.c_str()), (int)(responseStr.length()));
}

bool ProxyServer::matchesDomain(HttpRequest * httpRequest) {

	string domainName = httpRequest->getHostNameInUrl();
	if (domainName.empty()){
		return true;
	}
	return Utils::caseInsCompare(domainName, serverConfig.servingDomain);
}


void ProxyServer::increamentThreadNumber()
{
	pthread_mutex_lock(&threadNumberMutex);
	this->threadNumber ++;
	pthread_mutex_unlock(&threadNumberMutex);
}

void ProxyServer::decrementThreadNumber()
{
	pthread_mutex_lock(&threadNumberMutex);
	this->threadNumber --;
	pthread_mutex_unlock(&threadNumberMutex);
}

int ProxyServer::getThreadNumber()
{
	return this->threadNumber;
}
